package junior.db.service;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import junior.db.dao.mybatis.base.BaseSelectMapper;
import junior.db.dao.mybatis.type.ResultType;
import junior.db.dao.query.AbstractQuery;
import junior.db.query.DefaultFilterExampleApplier;
import junior.db.query.FilterExampleApplier;
import junior.db.query.PageQueryArgs;
import junior.db.query.PageUtils;
import junior.db.query.PageVO;
import junior.db.query.QueryArgs;
import junior.util.reflect.ReflectUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author wubo
 * @date 2024/9/5 18:25
 */
public abstract class AbstractService<ID, PO, VO, Mapper extends BaseSelectMapper<PO>> implements IService<ID, PO, VO> {
    protected Class<? extends PO> entityClass;
    protected Class<? extends VO> resultClass;
    
    public AbstractService() {
        initEntityClass();
        initResultClass();
    }
    
    @Override
    public VO get(QueryArgs args) {
        VO vo = getMapper().getForObject(toQueryForGet(args), ResultType.of(resultClass));
        afterGet(vo, args);
        return vo;
    }
    
    @Override
    public PageVO<VO> listPage(PageQueryArgs args) {
        PageHelper.startPage(PageUtils.page2Start(args.getPage(), args.getLimit()), args.getLimit());
        List<VO> list = getMapper().listForObject(toQueryForListPage(args), new ResultType(resultClass));
        Page<VO> page = (Page) list;
        return new PageVO<>(page.getTotal(), page.getPages(), page.getResult());
    }
    
    @Override
    public List<VO> list(QueryArgs args) {
        List<VO> vo = getMapper().listForObject(toQueryForList(args), new ResultType(resultClass));
        afterList(vo, args);
        return vo;
    }
    
    @Override
    public VO getById(ID id) {
        VO vo = getMapper().getForObject(toQueryForGetById(id), ResultType.of(resultClass));
        afterGetById(vo, id);
        return vo;
    }
    
    @Override
    public <T> T getForType(QueryArgs args, Class<T> type) {
        if (String.class.isAssignableFrom(type)) {
            return (T) getMapper().getForString(toQueryForGetForType(args));
        } else if (Long.class.isAssignableFrom(type)) {
            return (T) getMapper().getForLong(toQueryForGetForType(args));
        } else if (Integer.class.isAssignableFrom(type)) {
            return (T) getMapper().getForInteger(toQueryForGetForType(args));
        } else if (Date.class.isAssignableFrom(type)) {
            return (T) getMapper().getForDate(toQueryForGetForType(args));
        } else if (BigDecimal.class.isAssignableFrom(type)) {
            return (T) getMapper().getForBigDecimal(toQueryForGetForType(args));
        } else if (Boolean.class.isAssignableFrom(type)) {
            return (T) getMapper().getForBoolean(toQueryForGetForType(args));
        }  else if (LocalDate.class.isAssignableFrom(type)) {
            return (T) getMapper().getForLocalDate(toQueryForGetForType(args));
        }  else if (LocalDateTime.class.isAssignableFrom(type)) {
            return (T) getMapper().getForLocalDateTime(toQueryForGetForType(args));
        }  else if (Map.class.isAssignableFrom(type)) {
            return (T) getMapper().getForMap(toQueryForGetForType(args));
        } else {
            return (T) getMapper().getForObject(toQueryForGetForType(args), new ResultType(type));
        }
    }
    
    @Override
    public <T> List<T> listForType(QueryArgs args, Class<T> type) {
        if (String.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForString(toQueryForListForType(args));
        } else if (Long.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForLong(toQueryForListForType(args));
        } else if (Integer.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForInteger(toQueryForListForType(args));
        } else if (Date.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForDate(toQueryForListForType(args));
        } else if (BigDecimal.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForBigDecimal(toQueryForListForType(args));
        } else if (Boolean.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForBoolean(toQueryForListForType(args));
        }  else if (LocalDate.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForLocalDate(toQueryForListForType(args));
        }  else if (LocalDateTime.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForLocalDateTime(toQueryForListForType(args));
        }  else if (Map.class.isAssignableFrom(type)) {
            return (List<T>) getMapper().listForMap(toQueryForListForType(args));
        } else {
            return (List<T>) getMapper().listForObject(toQueryForListForType(args), new ResultType(type));
        }
    }
    
    protected AbstractQuery<?> toQueryForGet(QueryArgs args) {
        AbstractQuery<?> query = getQuery();
        applyArgs(args, query);
        return query;
    }
    
    protected VO afterGet(VO vo, QueryArgs args) {
        return vo;
    }
    
    protected AbstractQuery<?> toQueryForListPage(PageQueryArgs args) {
        AbstractQuery<?> query = getQuery();
        applyArgs(args, query);
        return query;
    }
    
    protected PageVO<VO> afterListPage(PageVO<VO> vo, PageQueryArgs args) {
        return vo;
    }
    
    protected AbstractQuery<?> toQueryForList(QueryArgs args) {
        AbstractQuery<?> query = getQuery();
        applyArgs(args, query);
        return query;
    }
    
    protected List<VO> afterList(List<VO> list, QueryArgs args) {
        return list;
    }
    
    protected AbstractQuery<?> toQueryForGetById(ID id) {
        AbstractQuery<?> query = getQuery();
        query.eq(idField(), id);
        return query;
    }
    
    protected VO afterGetById(VO vo, ID id) {
        return vo;
    }
    
    protected AbstractQuery<?> toQueryForGetForType(QueryArgs args) {
        AbstractQuery<?> query = getQuery();
        applyArgs(args, query);
        return query;
    }
    
    protected AbstractQuery<?> toQueryForListForType(QueryArgs args) {
        AbstractQuery<?> query = getQuery();
        applyArgs(args, query);
        return query;
    }
    
    protected void initEntityClass() {
        entityClass = (Class<PO>) ReflectUtils.getSuperClassGenericType(this.getClass(), getEntityClassGenericTypeIndex());
    }
    
    protected void initResultClass() {
        resultClass = (Class<VO>) ReflectUtils.getSuperClassGenericType(this.getClass(), getResultClassGenericTypeIndex());
    }
    
    protected int getEntityClassGenericTypeIndex() {return 1;}
    
    protected int getResultClassGenericTypeIndex() {return 2;}
    
    protected void applyArgs(QueryArgs args, AbstractQuery<?> query) {
        FilterExampleApplier applier = getFilterExampleApplier();
        if (args.getSelectFields() != null) {
            query.selects(args.getSelectFields());
        }
        applier.apply(args.getFilters(), query);
        if (args.getSorts() != null) {
            args.getSorts().forEach(query::sort);
        }
    }
    
    protected FilterExampleApplier getFilterExampleApplier() {
        return DefaultFilterExampleApplier.builder().build();
    }
    
    protected abstract AbstractQuery<?> getQuery();
    
    protected abstract Mapper getMapper();
    
    protected abstract String idField();
}
